<?php

declare(strict_types=1);

namespace app\service\notify;

use app\model\NotifyTemplate;
use think\facade\Log;
use app\model\NotifyMessage;
use app\model\NotifyRecipient;
use InvalidArgumentException;

class CreateNotifyService
{
    /**
     * 通过模板创建通知
     *
     * @param array $targetPairs 目标接收者
     * @param string $type 模板类型
     * @param array $data 模板填充数据
     * @param array $options 扩展选项: title, content, payload, template_id
     */
    public function add($targetPairs, $type, $data, array $options = [])
    {
        // 获取通知模板
        $template = NotifyTemplate::where('type', $type)->find();

        if (!$template) {
            Log::warning("通知模板未找到: {$type}");
            // 使用默认模板
            $title = '系统通知';
            $content = '您有一条新通知';
            $route = '';
        } else {
            $title = $this->replacePlaceholders($template->title, $data);
            $content = $this->replacePlaceholders($template->content, $data);
            $route = $this->getRouteForChannel($template->channels, 'websocket', $data);
        }

        if (isset($options['title']) && is_string($options['title']) && $options['title'] !== '') {
            $title = $options['title'];
        }
        if (isset($options['content']) && is_string($options['content']) && $options['content'] !== '') {
            $content = $options['content'];
        }

        $extraPayload = [];
        if (isset($options['payload']) && is_array($options['payload'])) {
            $extraPayload = $options['payload'];
        }

        $templateId = $template ? (int) $template->id : 0;
        if (array_key_exists('template_id', $options) && $options['template_id']) {
            $templateId = (int) $options['template_id'];
        }

        $recipients = $this->buildRecipients($targetPairs);
        if ($recipients === []) {
            throw new InvalidArgumentException('通知接收者不能为空');
        }

        $payload = $this->buildPayload(
            is_array($data) ? $data : [],
            $route,
            $extraPayload
        );

        // 创建通知消息（一条消息可以发送给多个接收者）
        $message = NotifyMessage::create([
            'template_id' => $templateId,
            'title' => $title,
            'content' => $content,
            'payload' => $payload,
            'status' => NotifyMessage::STATUS_PENDING,
        ]);

        /** @var NotifyMessage $message */
        $message->recipients()->saveAll($recipients);
        return ['message' => $message, 'recipients' => $recipients];
    }

    /**
     * 手动创建通知，无需模板
     *
     * @param array $targetPairs 目标接收者
     * @param string $title 通知标题
     * @param string $content 通知内容
     * @param array $payload 附加载荷
     * @param int|null $templateId 关联模板ID（可选）
     */
    public function addManual(array $targetPairs, string $title, string $content, array $payload = [], ?int $templateId = null): array
    {
        $recipients = $this->buildRecipients($targetPairs);
        if ($recipients === []) {
            throw new InvalidArgumentException('通知接收者不能为空');
        }

        $payload = $this->buildPayload([], null, $payload);

        /** @var NotifyMessage $message */
        $message = NotifyMessage::create([
            'template_id' => $templateId ? (int) $templateId : 0,
            'title' => $title,
            'content' => $content,
            'payload' => $payload,
            'status' => NotifyMessage::STATUS_PENDING,
        ]);

        $message->recipients()->saveAll($recipients);

        return ['message' => $message, 'recipients' => $recipients];
    }

    /**
     * 获取指定渠道的路由
     * @param NotifyTemplate $template 模板
     * @param string $channel 渠道
     * @param array $data 数据
     * @return string 路由
     */
    protected function getRouteForChannel($channels, $channel, $data)
    {
        if (empty($channels)) {
            return '';
        }

        if (is_string($channels)) {
            $decoded = json_decode($channels, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                $channels = $decoded;
            } else {
                return '';
            }
        }

        if (!isset($channels[$channel]['route'])) {
            return '';
        }

        $route = $channels[$channel]['route'];
        return $this->replacePlaceholders($route, $data);
    }

    /**
     * 替换模板中的占位符
     * @param string $template 模板内容
     * @param array $data 数据
     * @return string
     */
    protected function replacePlaceholders($template, $data)
    {
        foreach ($data as $key => $value) {
            if (is_scalar($value)) {
                $template = str_replace('{' . $key . '}', (string)$value, $template);
            }
        }
        return $template;
    }

    /**
     * 构建通知接收者数据
     */
    protected function buildRecipients(array $targetPairs): array
    {
        $recipients = [];
        foreach ($targetPairs as $pair) {
            $targetId = $pair['target_id'] ?? $pair['id'] ?? null;
            $targetType = $pair['target_type'] ?? $pair['type'] ?? null;
            if ($targetType === null) {
                continue;
            }

            $targetType = (string) $targetType;
            $channel = (string) ($pair['channel'] ?? 'websocket');

            if ($targetType === '') {
                continue;
            }

            $recipients[] = [
                'target_id' => $targetId ? (int) $targetId : 0,
                'target_type' => $targetType,
                'status' => NotifyRecipient::STATUS_PENDING,
                'channel' => $channel !== '' ? $channel : 'websocket',
                'send_attempts' => 0,
            ];
        }

        return $recipients;
    }

    /**
     * 构建消息载荷
     */
    protected function buildPayload(array $data, ?string $route, array $extraPayload = []): array
    {
        $payload = $data;

        if ($extraPayload !== []) {
            $payload = array_merge($payload, $extraPayload);
        }

        if ($route !== null) {
            $payload['route'] = $route;
        } elseif (!array_key_exists('route', $payload)) {
            $payload['route'] = '';
        }

        return $payload;
    }
}
